#include "CamPrimeraPersona.h"

#include <glm\gtc\matrix_transform.hpp>


CamPrimeraPersona::CamPrimeraPersona(unsigned int alto, unsigned int ancho, float fov) :
	Camara(alto, ancho, fov)
{
}


CamPrimeraPersona::~CamPrimeraPersona() { }


//-----------------------------------------------------------------------------

void CamPrimeraPersona::zoomIn(ZOOM_TYPE z){
	switch (z){
		case ZOOM_TYPE::ZOOM_IN:
			this->m_fov -= 5.0f;
			break;

		case ZOOM_TYPE::ZOOM_OUT:
			this->m_fov += 5.0f;
			break;

		case ZOOM_TYPE::ZOOM_NONE:
			this->m_fov = 45.0f;
			break;
	}

	actualizarMatrizProyeccion();
}


void CamPrimeraPersona::moverCamara(MOVE_TYPE direccion, float magnitud) {
	// Datos auxiliares que sirven para los calculos
	glm::vec3 vecDir = glm::normalize( this->m_vecTarget - this->m_vecPosicion );
	glm::vec3 vecRight = glm::normalize( glm::cross(vecDir, this->m_vecUp) );

	// Entre adelante y atras (y derecha e izquierda) hay mucho codigo similar
	// pero se deja como esta para que quede mas claro que es lo que pasa.
	switch (direccion){
		case MOVE_TYPE::ADELANTE:
			this->m_vecTarget = this->m_vecTarget + magnitud * vecDir;
			this->m_vecPosicion = this->m_vecPosicion + magnitud * vecDir;
			break;

		case MOVE_TYPE::ATRAS:
			this->m_vecTarget = this->m_vecTarget - magnitud * vecDir;
			this->m_vecPosicion = this->m_vecPosicion - magnitud * vecDir;
			break;

		case MOVE_TYPE::DERECHA:
			this->m_vecTarget = this->m_vecTarget + magnitud * vecRight;
			this->m_vecPosicion = this->m_vecPosicion + magnitud * vecRight;
			break;

		case MOVE_TYPE::IZQUIERDA:
			this->m_vecTarget = this->m_vecTarget - magnitud * vecRight;
			this->m_vecPosicion = this->m_vecPosicion - magnitud * vecRight;
			break;
	}

	// Despues de los cambios actualiza las matrices
	actualizarMatrizVista();
}

//-----------------------------------------------------------------------------

void CamPrimeraPersona::mouseMove(int viewportPosX, int viewportPosY) {
	// Evita que el seteo del mouse en el centro de la pantalla rompa todo
	if (viewportPosX == this->m_ancho / 2 && viewportPosY == this->m_alto / 2) return;

	// El desvio en radianes desde el centro de la ventana
	float dx = fromPosXToRadians(viewportPosX);
	float dy = fromPosYToRadians(viewportPosY);

	// En base al vector director y a los deltas "X" e "Y" se define un nuevo vector director
	glm::vec3 vecDirector = this->m_vecTarget - this->m_vecPosicion;
	glm::vec3 nuevoVecDirector = crearNuevoVecDirector(vecDirector, dx, dy);

	// Calcula el nuevo vector target y lo actualiza
	this->m_vecTarget = nuevoVecDirector + this->m_vecPosicion;

	actualizarMatrizVista();

	// Permite un mejor movimiento de la camara al setear el mouse en el centro de la ventana
	glutWarpPointer(this->m_ancho/2, this->m_alto/2);
}


float CamPrimeraPersona::fromPosXToRadians(int x) const {
	// Positivo hacia la derecha
	float posRelativaX = static_cast<float>( (this->m_ancho / 2) - x );

	float radX = (posRelativaX / this->m_ancho) * glm::radians(this->m_fov);
	return radX;
}


float CamPrimeraPersona::fromPosYToRadians(int y) const {
	// Calcula el FOV en el eje vertical
	float aspectRatio = static_cast<float>(this->m_ancho) / static_cast<float>(this->m_alto);
	float fov_rad_V = glm::radians(this->m_fov) / aspectRatio;

	// Positivo hacia arriba
	float posRelativaY = static_cast<float>( y - (this->m_alto / 2) );
	float radY = (posRelativaY / this->m_alto) * fov_rad_V;
	return radY;
}


glm::vec3 CamPrimeraPersona::crearNuevoVecDirector(const glm::vec3 &oldVecDirector, float deltaX, float deltaY) const {
	// Se pasa el viejo vector a coordenadas esfericas
	float ro_0 = sqrt( pow(oldVecDirector.x, 2) + pow(oldVecDirector.y, 2) + pow(oldVecDirector.z, 2) );
	float fi_0 = atan2( oldVecDirector.y, oldVecDirector.x );
	float tita_0 = acos( oldVecDirector.z / ro_0);

	// Se modifican los parametros del viejo vector con los deltas "X" e "Y"
	float ro_1 = ro_0;	// No cambia la norma del vector
	float fi_1 = fi_0 + deltaX;
	float tita_1 = limitarAnguloVert(tita_0 + deltaY);

	// Se pasa de coordenadas esfericas a cartesianas y se obtiene un nuevo vector director
	float x_1 = ro_1 * sin(tita_1) * cos(fi_1);
	float y_1 = ro_1 * sin(tita_1) * sin(fi_1);
	float z_1 = ro_1 * cos(tita_1);
	glm::vec3 vecDirector(x_1, y_1, z_1);

	return vecDirector;
}


// Los limites del angulo vertical para evitar calcular el vector "up"
float CamPrimeraPersona::limitarAnguloVert(float angulo) const {
	const float limSup = 15.0f * 3.1416f / 16.0f;	// 15/16 de PI
	const float limInf = 3.1416f / 16.0f;	// 1/16 de PI

	if (angulo > limSup) return limSup;
	if (angulo < limInf) return limInf;

	return angulo;
}

